import {renderForm} from "./hooks/tpllib";
import {createFormModel} from "./form";
import {createFieldMixin} from "./cm-field-core";

function renderCOM(formCONFIG) {
  let partStr = {};
  if (Array.isArray(formCONFIG.parts)) {
    formCONFIG.parts.forEach((part, index) => {
      // console.dir(part.def, {
      //   depth: null
      // });
      const modelKey = 'parts.' + part.name + '.model';
      const partConfigKey = 'config.parts[' + index + '].def';

      let _tpl = renderForm(part.def, modelKey, partConfigKey,
        { part, BASE_PATH: modelKey, CONFIG: formCONFIG, partKey: `parts.${part.name}` })

      let __INIT_ATTR__ = ''

      if (Array.isArray(part?.def?.ui?.attrs) && part?.def?.ui?.attrs.length > 0) {
        // console.log(part?.def?.ui?.attrs)
        let keys = part?.def?.ui?.attrs.map(v => v[0])
        if (!keys.includes('label-width')) {
          __INIT_ATTR__ = 'label-width="8em"'
        }
      } else {
        __INIT_ATTR__ = 'label-width="8em"'
      }

      // _tpl.replace('__INIT_ATTR__', __INIT_ATTR__)

      // console.log('_tpl', _tpl)

      partStr[part.name] = {
        _tpl,
        attr: __INIT_ATTR__,
      }
    })
  }
  // console.log(partStr)
  return partStr;
}

let fieldMixinDefMap = new Map();
export function defZFormFieldCom(name, value) {
  fieldMixinDefMap.set(name, value)
}

export function getFieldMixinDefMap() {
  return fieldMixinDefMap
}



/**
 * 注册组件到app上
 * @param app
 */
export function registerFormComToApp(CustomDymComponent) {
  fieldMixinDefMap.forEach(function (item, name) {
    // console.log(item.create(), name)
    CustomDymComponent.registerOnce(item.create(), name)
  })
}

function flattenObject(ob) {
  var toReturn = {};

  for (var i in ob) {
    // eslint-disable-next-line no-prototype-builtins
    if (!ob.hasOwnProperty(i)) continue;

    if ((typeof ob[i]) == 'object' && ob[i] !== null) {
      var flatObject = flattenObject(ob[i]);
      for (var x in flatObject) {
        // eslint-disable-next-line no-prototype-builtins
        if (!flatObject.hasOwnProperty(x)) continue;

        toReturn[i + '.' + x] = flatObject[x];
      }
    } else {
      toReturn[i] = ob[i];
    }
  }
  return toReturn;
}

/**
 * 配置转变为form组件
 * @param comName
 * @param config
 * @param tpl
 * @param outerCtx
 * @param outerProps
 * @returns {propConfig|{updateValue: updateValue, slotContent: (*|{default: []}), parts: {}, fieldMixin(*): (*), getUI_CONFIG: (function(*): *), config, registerWatchHandle: registerWatchHandle, exportCtx: {submit(*): Promise<*>, getMetas(): *, getRawData(*): *}}|*|{template: *, methods: {getRef(*): Vue | Element | (Vue | Element)[]}, name, setup(*, *): {updateValue: updateValue, slotContent: (*|{default: []}), parts: {}, fieldMixin(*): (*), getUI_CONFIG: (function(*): *), config, registerWatchHandle: registerWatchHandle, exportCtx: {submit(*): Promise<*>, getMetas(): *, getRawData(*): *}}, props: {modelValue: null, render: null}}|Promise<unknown>|{ui}|Vue|Element|(Vue | Element)[]|{template: `<div>${string|*}</div>`, data(): {ui: *}}}
 */
export function configToFormComponent(comName, config, tpl, {
  outerCtx,
  // partName,
  outerProps
} = {}) {
  const {provide, reactive, getCurrentInstance, watch, inject} = globalThis.vueCompositionAPI

  let compileData = {}

  compileData.CONFIG = config;
  compileData.partStr = renderCOM(config);

  // console.log(config)
  let str = Object.entries(compileData.partStr)[0][1]
  // console.log(str)

  if (!globalThis.Twig) {
    console.error('need Twig.js')
  }

  let t = globalThis.Twig.twig({
    // id: tplID,
    data: tpl,
    allowInlineIncludes: true
  });
  let html = t.render(compileData)
  const PREFIX = 'comformscr2__'

  let partConfig = {}
  let partName = outerProps.partName;
  // 兼容老版本
  if (!partName && Array.isArray(compileData.CONFIG.parts) && compileData.CONFIG.parts[0]) {
    partName = compileData.CONFIG.parts[0].name
  }
  if (Array.isArray(compileData.CONFIG.parts) && compileData.CONFIG.parts[0]) {
    partConfig = compileData.CONFIG.parts[0]
  }
let properties = ZY.lodash.get(partConfig, 'def.properties')


  // console.log(partConfig, properties)

  let formRules = {
    // name: [
    //   { required: true, message: '请输入活动名称', trigger: 'blur' },
    //   { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
    // ],
  }

  ZY.lodash.each(properties, (property, propertyName) => {
    // console.log(property, propertyName)
    if (property.rules) {
      formRules[propertyName] = property.rules
    }
  })
  // console.log(formRules)


  // console.log(html)
  return {
    name: comName,
    template: html,
    props: {
      modelValue: null,
      render: null
    },
    inject: ['zformLoader'],
    methods: {
      // getRef(partName) {
      //   return this.$refs[PREFIX + partName]
      // }
    },
    data() {
      return {
        private_zFormMeta: {}
      }
    },
    mounted() {
      // console.log(this.zformLoader)
      this.private_zFormMeta = globalThis.ZForm.getZFormMeta (this);
      this.zformLoader.registerCur(this)
    },
    setup(props, ctx) {

      let instanse = getCurrentInstance()
      let zFormMeta = globalThis.ZForm.getZFormMeta (instanse)
      let JSON5 = ZY.JSON5;
      let lodash = ZY.lodash;

      let cmFields = new Map()
      let cmRefsMap = new Map()


      let locked = true;



      let uuid = PREFIX + ZY.rid(10);
      // console.log(instanse)

      function getFormRef(partName) {
        return instanse.refs[PREFIX + partName]
      }

      /**
       * 存放配置里有computedFun的属性
       * @type {Map<any, any>}
       */
      let watchHandleMap = new Map();
      function registerWatchHandle(key, value) {
        watchHandleMap.set(key, value)
      }

      /**
       * 初始化
       * @param partDef
       * @returns {{model: UnwrapRef<{}>}}
       */
      function initPart(partDef) {
        let cachedModel = '{}';
        let rowDef = partDef.def;
        let obj = createFormModel(rowDef)
        // console.log(obj, rowDef)
        let model = reactive(obj);
        let modelLocks = false;
        // console.log('initPart', model)

        watch(model, (newVal, oldVal) => {
          let newObj = JSON5.parse(JSON5.stringify(newVal));
          // console.log(newObj)
          let objKeys = Object.keys(newObj)
          let oldObj = JSON5.parse(cachedModel)
          let diffed = ZY.diff(oldObj, newObj);
          let flattenD = flattenObject(diffed);

          if (!modelLocks) {
            // console.log('watch', flattenD)
            let flattenDKeys = Object.keys(flattenD)
            watchHandleMap.forEach(function (item, key) {
              let isContains = lodash.difference(key, objKeys).length < 1;
              let flattenDHas = lodash.difference(flattenDKeys, key).length < 1;
              // console.log(flattenDHas, key, flattenDKeys)
              if (isContains && flattenDHas) {
                console.log('changed', key, )
                modelLocks = true
                item.run(model);
                setTimeout(() => {
                  modelLocks = false
                }, 300)
              }
              // console.log(objKeys, key, isContains)
            })

          }

          cachedModel = JSON5.stringify(newObj)
        })

        return {
          model
        }
      }

      let parts = {};
      if (Array.isArray(config.parts)) {
        config.parts.forEach(part => {
          parts[part.name] = initPart(part)
        })
      }

      let registerHookMap = new Map()
      function registerHook(name, key, value) {
        let map = registerHookMap.get(name)
        if (!map) {
          map = new Map()
        }
        map.set(key, value)
        registerHookMap.set(name, map)
      }

      function unRegisterHook(name, key) {
        let map = registerHookMap.get(name)
        if (!map) {
          return
        }
        map.delete(key)
        registerHookMap.set(name, map)
      }


      function getCmFieldContext(key) {
        let def = cmFields.get(key)
        if (def) {
          return def.getWidgetContext()
        }
      }


      function setValueChange() {

      }

      function offValueChange() {

      }

      // console.log(parts, partName)

      function afterUpdateValue(partName, model, s_path, pathArr, e) {
        console.log('updateValue', model, s_path, pathArr, e)
        let afterUpdateHookMap = registerHookMap.get('afterUpdate')
        if (afterUpdateHookMap) {
          // let afterUpdateHook = afterUpdateHookMap.get(s_path)
          // console.log(afterUpdateHook, afterUpdateHookMap, s_path)
          // if (afterUpdateHook) {
          //   afterUpdateHook({partName, model, s_path, pathArr, getCmFieldContext, cmFields, e})
          // }

          afterUpdateHookMap.forEach(afterUpdateHook => {
            afterUpdateHook({partName, model, s_path, pathArr, getCmFieldContext, cmFields, e, ctx})
          })
        }
      }

      function getUI_CONFIG(path) {
        return lodash.get({
          config
        }, path)
      }

      let slotContent = (function() {
        if (props.render) {
          return props.render()
        }
        // console.log(outerCtx)
        if (outerCtx.slots) {
          return outerCtx.slots
        }
        return {
          default: []
        }
      })()

      function updateValue(partName = '', pathArr = [], e) {
        let s_path = ZY.getObjPathFromPathArr(pathArr);
        let model = parts[partName].model;
        lodash.set(model, s_path, e);
        afterUpdateValue(partName, model, s_path, pathArr, e)
      }

      function updateArrValue(partName = '', pathArr = [], e) {
        // let s_path = ZY.getObjPathFromPathArr(pathArr);
        // let model = parts[partName].model;
        // console.log('updateArrValue', partName, model, s_path, e)
        updateValue(partName, pathArr, e)
      }



      /**
       * @private
       * @returns {Map<any, any>}
       */
      function getCmFields() {
        return cmFields
      }

      function getRefCmField(ref = '') {
        let obj = cmRefsMap.get(ref)
        if (obj && obj.getWidgetContext) {
          return {
            context:  obj.getWidgetContext(),
            ui: obj.props.ui,
            self_path: obj.props.self_path,
          }
        }
      }


      let exportCtx = {
        // getCmFields,
        getRefCmField,
        getConfig() {
          return partConfig
        },
        /**
         * 导入数据
         * @param data
         * @returns {Promise<void>}
         */
        async importData(data = {}) {
          if (data) {
            locked = false
            // parts[partName].model = data
            let model = parts[partName].model
            // console.log('importData', data, parts[partName].model)
            ZY.lodash.each(data, function (item, key) {
              model[key] = item
            })
            setTimeout(() => {
              locked = true
            }, 30)
          }
        },
        /**
         * 单次设置model
         * @param path
         * @param data
         * @returns {Promise<void>}
         */
        async setModelByPath(path, data) {
          let model = parts[partName].model
          ZY.lodash.set(model, path, data)
        },
        async setRefField(ref = '', data) {
          let field = getRefCmField(ref)
          if (field) {
            exportCtx.setModelByPath(field.self_path, data)
          }
        },
        /**
         * 提交
         * @returns {Promise<unknown>}
         */
        async submit() {
          // let self = this;
          return new Promise(resolve => {
            let form = getFormRef(partName);
            // form.resetFields()
            form.validate(function (isValid, errors = []) {
              // console.log(isValid)
              resolve([isValid, errors])
            });
          })
        },
        /**
         *
         * @returns {Promise | Promise<unknown>}
         */
        getRawData() {
          let observed =  parts[partName].model
          // console.log(observed)
          return ZY.structuralClone(observed)
        },
        /**
         *
         * @returns {*}
         */
        getDataSync() {
          let observed =  parts[partName].model
          console.log('getDataSync', observed)
          return ZY_EXT.structuredClone(observed)
        },
        /**
         *
         * @returns {*}
         */
        getMetas() {
          return outerProps?.metas
        }
      }

      /**
       * 获取对象深度
       * @param v
       * @returns {number|*}
       */
      function getObjDeep(v) {
        // console.log('getObjDeep', v)
        if (Array.isArray(v)) {
          return v.length
        }
        return -1
      }

      function getIntVal(v) {
        if (Array.isArray(v)) {
          return v[0]
        }
        return undefined
      }

      function getModel() {
        return parts[partName].model
      }

      function getRawModel() {
        return ZY.JSON5.parse(
          ZY.JSON5.stringify(
            parts[partName].model ? parts[partName].model : {}
          )
        )
      }

      /**
       * 注册cm-field
       * @param def
       * @param key
       * @param value
       */
      function register(def, key, value) {
        cmFields.set(key, value)
        zFormMeta.CustomDymComponent.register(def)
        // console.log(cmFields)
        if (value?.props?.ui?.ref) {
          cmRefsMap.set(value.props.ui.ref, value)
        }
      }

      /**
       * 解除cm-field注册
       * @param def
       * @param key
       */
      function unRegister(def, key) {
        cmFields.delete(key)
        zFormMeta.CustomDymComponent.unRegister(def)
      }

      /**
       * 使用lodash get
       * @param obj
       * @param path
       * @param defaultVal
       * @returns {*}
       */
      function obj_get(obj, path, defaultVal) {
        return ZY.lodash.get(obj, path, defaultVal)
      }


      let comIns = {
        uuid,
        config,
        exportCtx,
        getRawModel,
        setValueChange,
        offValueChange,
        obj_get,
        parts,
        registerWatchHandle,
        slotContent,
        register,
        unRegister,
        registerHook,
        getRefCmField,
        updateArrValue,
        unRegisterHook,
        updateValue,
        getCmFields,
        getModel,
        formRules,
        getObjDeep,
        getUI_CONFIG,
        getLocked() {
          return locked
        },
        getIntVal,
        fieldMixin: createFieldMixin()
      }


      provide('curFormCon', comIns)

      return comIns
    }
  }
}
